#ifndef __SPD_LOGGER_H__
#define __SPD_LOGGER_H__

#include <string>
#include <sstream>
#include <unistd.h>

#ifndef SPDLOG_ACTIVE_LEVEL
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
#endif

#include "spdlog/spdlog.h"
#include "spdlog/fmt/fmt.h"
#include "spdlog/fmt/bundled/printf.h"
#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"

class SPDLogger final
{
public:
    static SPDLogger *getInstance(void)
    {
        return &getReference();
    }
    static SPDLogger &getReference()
    {
        static SPDLogger logger;
        return logger;
    }

    void outEnable(bool enable)
    {
        if(m_enable == enable)
            return ;
        m_enable = enable;
        if(m_enable)
            spdlog::set_level(m_level);
        else
            spdlog::set_level(spdlog::level::off);
    }

    void setLevel(const std::string &level)
    {
        if(!m_init_flag)
            return;
        if(m_enable)
        {
            m_level = stringTolevel(level);
            spdlog::set_level(stringTolevel(level));
        }
    }

    void flushLevel(const std::string &level)
    {
        if(!m_init_flag)
            return;
        spdlog::flush_on(stringTolevel(level));
    }

    void flushEvery(int second)
    {
        if(!m_init_flag)
            return;
        spdlog::flush_every(std::chrono::seconds(second));
    }

    void setPattern(const std::string &pattern)
    {
        if(!m_init_flag)
            return ;
        spdlog::set_pattern(pattern);
    }

    int consoleLoggerInit(const std::string &process_name)
    {
        if(m_init_flag)
            return 0;
        std::vector<spdlog::sink_ptr> sinks;
        sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
        loggerInit(process_name, sinks, spdlog::level::debug);
        return 0;
    }

    int dailyLoggerInit(const std::string &process_name, std::string &log_path, bool console, int hour = 0, int minute = 0)
    {
        if(m_init_flag)
            return 0;
        if(hour < 0 || hour > 23 || minute < 0 || minute > 59)
            return -1;
        if(0 != access(log_path.c_str(), F_OK | W_OK))
            return consoleLoggerInit(process_name);

        std::vector<spdlog::sink_ptr> sinks;
        std::string path = log_path + "/" + process_name + ".log";
        if(console)
            sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
        sinks.push_back(std::make_shared<spdlog::sinks::daily_file_sink_mt>(path, hour, minute));
        loggerInit(process_name, sinks, spdlog::level::info);
        return 0;
    }

    int rotatingLoggerInit(const std::string &process_name, std::string &log_path, bool console, int size = 5*1024*1024, int count = 3)
    {
        if(m_init_flag)
            return 0;
        if(size < 0 || count < 0)
            return -1;
        if((0 != access(log_path.c_str(), F_OK | W_OK)) || (0 == size) || (0 == count))
            return consoleLoggerInit(process_name);

        std::vector<spdlog::sink_ptr> sinks;
        std::string path = log_path + "/" + process_name + ".log";
        if(console)
            sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
        sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(path, size, count));
        loggerInit(process_name, sinks, spdlog::level::info);
        return 0;
    }

    template <typename... Args>
    static void print(const spdlog::source_loc &loc, spdlog::level::level_enum lvl, const char *fmt, const Args &... args)
    {
        spdlog::log(loc, lvl, fmt::sprintf(fmt, args...).c_str());
    }

    class logStream : public std::ostringstream
    {
    public:
        explicit logStream(const spdlog::source_loc &loc, spdlog::level::level_enum lvl) : m_loc(loc), m_lvl(lvl) {}
        ~logStream() { flush(); }
        void flush() { spdlog::log(m_loc, m_lvl, str().c_str()); }
    private:
        spdlog::source_loc m_loc;
        spdlog::level::level_enum m_lvl = spdlog::level::info;
    };


private:
    void loggerInit(const std::string &process_name, std::vector<spdlog::sink_ptr> &sinks, spdlog::level::level_enum level)
    {
        try {
            m_level = level;
            auto logger = std::make_shared<spdlog::logger>(process_name, sinks.begin(), sinks.end());
            logger->flush_on(level);
            logger->set_level(level);
            spdlog::register_logger(logger);
            spdlog::set_default_logger(logger);
            spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^-%L-%$] [%s:%# %!()] : %v");
            m_init_flag = true;
        } catch (std::exception_ptr e) {
            assert(false);
        }
    }
    spdlog::level::level_enum stringTolevel(const std::string &level)
    {
        std::string level_lower(level);
        spdlog::level::level_enum log_level = spdlog::level::info;
        std::transform(level_lower.begin(), level_lower.end(), level_lower.begin(), ::tolower);
        if(level_lower == "trace")
            log_level = spdlog::level::trace;
        else if(level_lower == "debug")
            log_level = spdlog::level::debug;
        else if(level_lower == "info")
            log_level = spdlog::level::info;
        else if(level_lower == "warn")
            log_level = spdlog::level::warn;
        else if(level_lower == "error")
            log_level = spdlog::level::err;
        else if(level_lower == "fatal")
            log_level = spdlog::level::critical;
        return log_level;
    }

private:
    SPDLogger() { m_init_flag = false; }
    ~SPDLogger() { spdlog::drop_all(); }
    SPDLogger(SPDLogger &&) = delete;
    SPDLogger(const SPDLogger &) = delete;
    SPDLogger &operator=(SPDLogger &&) = delete;
    SPDLogger &operator=(const SPDLogger &) = delete;

private:
    bool m_enable = true;
    bool m_init_flag = false;
    spdlog::level::level_enum m_level;
};

///< use fmt lib, e.g. LOG_INFO("info log, {1}, {2}, {1}", 1, 2)
#define LOG_TRACE(msg,...)      spdlog::log({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::trace, msg, ##__VA_ARGS__)
#define LOG_DEBUG(msg,...)      spdlog::log({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::debug, msg, ##__VA_ARGS__)
#define LOG_INFO(msg,...)       spdlog::log({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::info, msg, ##__VA_ARGS__)
#define LOG_WARN(msg,...)       spdlog::log({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::warn, msg, ##__VA_ARGS__)
#define LOG_ERROR(msg,...)      spdlog::log({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::err, msg, ##__VA_ARGS__)
#define LOG_FATAL(msg,...)      spdlog::log({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::critical, msg, ##__VA_ARGS__)

///< use like printf, e.g. PLOG_INFO("info log, %d-%d", 1, 2)
#define PLOG_TRACE(fmt,...)     SPDLogger::print({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::trace, fmt, ##__VA_ARGS__)
#define PLOG_DEBUG(fmt,...)     SPDLogger::print({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::debug, fmt, ##__VA_ARGS__)
#define PLOG_INFO(fmt,...)      SPDLogger::print({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::info, fmt, ##__VA_ARGS__)
#define PLOG_WARN(fmt,...)      SPDLogger::print({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::warn, fmt, ##__VA_ARGS__)
#define PLOG_ERROR(fmt,...)     SPDLogger::print({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::err, fmt, ##__VA_ARGS__)
#define PLOG_FATAL(fmt,...)     SPDLogger::print({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::critical, fmt, ##__VA_ARGS__)

///< use like stream, e.g. SLOG_INFO() << "warn log: " << 1
#define SLOG_TRACE()            SPDLogger::logStream({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::trace)
#define SLOG_DEBUG()            SPDLogger::logStream({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::debug)
#define SLOG_INFO()             SPDLogger::logStream({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::info)
#define SLOG_WARN()             SPDLogger::logStream({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::warn)
#define SLOG_ERROR()            SPDLogger::logStream({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::err)
#define SLOG_FATAL()            SPDLogger::logStream({__FILE__, __LINE__, __FUNCTION__}, spdlog::level::critical)

#endif
